// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <stdint.h>

#include <memory>
#include <string>

#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "content/public/test/async_file_test_helper.h"
#include "content/public/test/test_file_system_context.h"
#include "storage/browser/fileapi/async_file_util_adapter.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/file_system_file_util.h"
#include "storage/browser/fileapi/file_system_operation_context.h"
#include "storage/browser/fileapi/local_file_util.h"
#include "storage/browser/fileapi/native_file_util.h"
#include "storage/common/fileapi/file_system_types.h"
#include "testing/gtest/include/gtest/gtest.h"

using content::AsyncFileTestHelper;
using storage::AsyncFileUtilAdapter;
using storage::FileSystemContext;
using storage::FileSystemOperationContext;
using storage::FileSystemURL;
using storage::LocalFileUtil;

namespace content {

namespace {

    const GURL kOrigin("http://foo/");
    const storage::FileSystemType kFileSystemType = storage::kFileSystemTypeTest;

} // namespace

class LocalFileUtilTest : public testing::Test {
public:
    LocalFileUtilTest() { }

    void SetUp() override
    {
        ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
        file_system_context_ = CreateFileSystemContextForTesting(NULL, data_dir_.GetPath());
    }

    void TearDown() override
    {
        file_system_context_ = NULL;
        base::RunLoop().RunUntilIdle();
    }

protected:
    FileSystemOperationContext* NewContext()
    {
        FileSystemOperationContext* context = new FileSystemOperationContext(file_system_context_.get());
        context->set_update_observers(
            *file_system_context_->GetUpdateObservers(kFileSystemType));
        return context;
    }

    LocalFileUtil* file_util()
    {
        AsyncFileUtilAdapter* adapter = static_cast<AsyncFileUtilAdapter*>(
            file_system_context_->GetAsyncFileUtil(kFileSystemType));
        return static_cast<LocalFileUtil*>(adapter->sync_file_util());
    }

    FileSystemURL CreateURL(const std::string& file_name)
    {
        return file_system_context_->CreateCrackedFileSystemURL(
            kOrigin, kFileSystemType, base::FilePath().FromUTF8Unsafe(file_name));
    }

    base::FilePath LocalPath(const char* file_name)
    {
        base::FilePath path;
        std::unique_ptr<FileSystemOperationContext> context(NewContext());
        file_util()->GetLocalFilePath(context.get(), CreateURL(file_name), &path);
        return path;
    }

    bool FileExists(const char* file_name)
    {
        return base::PathExists(LocalPath(file_name)) && !base::DirectoryExists(LocalPath(file_name));
    }

    bool DirectoryExists(const char* file_name)
    {
        return base::DirectoryExists(LocalPath(file_name));
    }

    int64_t GetSize(const char* file_name)
    {
        base::File::Info info;
        base::GetFileInfo(LocalPath(file_name), &info);
        return info.size;
    }

    base::File CreateFile(const char* file_name)
    {
        int file_flags = base::File::FLAG_CREATE | base::File::FLAG_WRITE | base::File::FLAG_ASYNC;

        std::unique_ptr<FileSystemOperationContext> context(NewContext());
        return file_util()->CreateOrOpen(context.get(), CreateURL(file_name),
            file_flags);
    }

    base::File::Error EnsureFileExists(const char* file_name,
        bool* created)
    {
        std::unique_ptr<FileSystemOperationContext> context(NewContext());
        return file_util()->EnsureFileExists(context.get(),
            CreateURL(file_name), created);
    }

    FileSystemContext* file_system_context()
    {
        return file_system_context_.get();
    }

private:
    base::MessageLoop message_loop_;
    scoped_refptr<FileSystemContext> file_system_context_;
    base::ScopedTempDir data_dir_;

    DISALLOW_COPY_AND_ASSIGN(LocalFileUtilTest);
};

TEST_F(LocalFileUtilTest, CreateAndClose)
{
    const char* file_name = "test_file";
    base::File file = CreateFile(file_name);
    ASSERT_TRUE(file.IsValid());
    ASSERT_TRUE(file.created());

    EXPECT_TRUE(FileExists(file_name));
    EXPECT_EQ(0, GetSize(file_name));

    std::unique_ptr<FileSystemOperationContext> context(NewContext());
}

// base::CreateSymbolicLink is only supported on POSIX.
#if defined(OS_POSIX)
TEST_F(LocalFileUtilTest, CreateFailForSymlink)
{
    // Create symlink target file.
    const char* target_name = "symlink_target";
    base::File target_file = CreateFile(target_name);
    ASSERT_TRUE(target_file.IsValid());
    ASSERT_TRUE(target_file.created());
    base::FilePath target_path = LocalPath(target_name);

    // Create symlink where target must be real file.
    const char* symlink_name = "symlink_file";
    base::FilePath symlink_path = LocalPath(symlink_name);
    ASSERT_TRUE(base::CreateSymbolicLink(target_path, symlink_path));
    ASSERT_TRUE(FileExists(symlink_name));

    // Try to open the symlink file which should fail.
    std::unique_ptr<FileSystemOperationContext> context(NewContext());
    FileSystemURL url = CreateURL(symlink_name);
    int file_flags = base::File::FLAG_OPEN | base::File::FLAG_READ;
    base::File file = file_util()->CreateOrOpen(context.get(), url, file_flags);
    ASSERT_FALSE(file.IsValid());
    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, file.error_details());
}
#endif

TEST_F(LocalFileUtilTest, EnsureFileExists)
{
    const char* file_name = "foobar";
    bool created;
    ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(file_name, &created));
    ASSERT_TRUE(created);

    EXPECT_TRUE(FileExists(file_name));
    EXPECT_EQ(0, GetSize(file_name));

    ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(file_name, &created));
    EXPECT_FALSE(created);
}

TEST_F(LocalFileUtilTest, TouchFile)
{
    const char* file_name = "test_file";
    base::File file = CreateFile(file_name);
    ASSERT_TRUE(file.IsValid());
    ASSERT_TRUE(file.created());

    std::unique_ptr<FileSystemOperationContext> context(NewContext());

    base::File::Info info;
    ASSERT_TRUE(base::GetFileInfo(LocalPath(file_name), &info));
    const base::Time new_accessed = info.last_accessed + base::TimeDelta::FromHours(10);
    const base::Time new_modified = info.last_modified + base::TimeDelta::FromHours(5);

    EXPECT_EQ(base::File::FILE_OK,
        file_util()->Touch(context.get(), CreateURL(file_name),
            new_accessed, new_modified));

    ASSERT_TRUE(base::GetFileInfo(LocalPath(file_name), &info));
    EXPECT_EQ(new_accessed, info.last_accessed);
    EXPECT_EQ(new_modified, info.last_modified);
}

TEST_F(LocalFileUtilTest, TouchDirectory)
{
    const char* dir_name = "test_dir";
    std::unique_ptr<FileSystemOperationContext> context(NewContext());
    ASSERT_EQ(base::File::FILE_OK,
        file_util()->CreateDirectory(context.get(),
            CreateURL(dir_name),
            false /* exclusive */,
            false /* recursive */));

    base::File::Info info;
    ASSERT_TRUE(base::GetFileInfo(LocalPath(dir_name), &info));
    const base::Time new_accessed = info.last_accessed + base::TimeDelta::FromHours(10);
    const base::Time new_modified = info.last_modified + base::TimeDelta::FromHours(5);

    EXPECT_EQ(base::File::FILE_OK,
        file_util()->Touch(context.get(), CreateURL(dir_name),
            new_accessed, new_modified));

    ASSERT_TRUE(base::GetFileInfo(LocalPath(dir_name), &info));
    EXPECT_EQ(new_accessed, info.last_accessed);
    EXPECT_EQ(new_modified, info.last_modified);
}

TEST_F(LocalFileUtilTest, Truncate)
{
    const char* file_name = "truncated";
    bool created;
    ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(file_name, &created));
    ASSERT_TRUE(created);

    std::unique_ptr<FileSystemOperationContext> context;

    context.reset(NewContext());
    ASSERT_EQ(base::File::FILE_OK,
        file_util()->Truncate(context.get(), CreateURL(file_name), 1020));

    EXPECT_TRUE(FileExists(file_name));
    EXPECT_EQ(1020, GetSize(file_name));
}

TEST_F(LocalFileUtilTest, CopyFile)
{
    const char* from_file = "fromfile";
    const char* to_file1 = "tofile1";
    const char* to_file2 = "tofile2";
    bool created;
    ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(from_file, &created));
    ASSERT_TRUE(created);

    std::unique_ptr<FileSystemOperationContext> context;
    context.reset(NewContext());
    ASSERT_EQ(base::File::FILE_OK,
        file_util()->Truncate(context.get(), CreateURL(from_file), 1020));

    EXPECT_TRUE(FileExists(from_file));
    EXPECT_EQ(1020, GetSize(from_file));

    ASSERT_EQ(base::File::FILE_OK,
        AsyncFileTestHelper::Copy(file_system_context(),
            CreateURL(from_file),
            CreateURL(to_file1)));

    context.reset(NewContext());
    ASSERT_EQ(base::File::FILE_OK,
        AsyncFileTestHelper::Copy(file_system_context(),
            CreateURL(from_file),
            CreateURL(to_file2)));

    EXPECT_TRUE(FileExists(from_file));
    EXPECT_EQ(1020, GetSize(from_file));
    EXPECT_TRUE(FileExists(to_file1));
    EXPECT_EQ(1020, GetSize(to_file1));
    EXPECT_TRUE(FileExists(to_file2));
    EXPECT_EQ(1020, GetSize(to_file2));
}

TEST_F(LocalFileUtilTest, CopyDirectory)
{
    const char* from_dir = "fromdir";
    const char* from_file = "fromdir/fromfile";
    const char* to_dir = "todir";
    const char* to_file = "todir/fromfile";
    bool created;
    std::unique_ptr<FileSystemOperationContext> context;

    context.reset(NewContext());
    ASSERT_EQ(base::File::FILE_OK,
        file_util()->CreateDirectory(context.get(), CreateURL(from_dir),
            false, false));
    ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(from_file, &created));
    ASSERT_TRUE(created);

    context.reset(NewContext());
    ASSERT_EQ(base::File::FILE_OK,
        file_util()->Truncate(context.get(), CreateURL(from_file), 1020));

    EXPECT_TRUE(DirectoryExists(from_dir));
    EXPECT_TRUE(FileExists(from_file));
    EXPECT_EQ(1020, GetSize(from_file));
    EXPECT_FALSE(DirectoryExists(to_dir));

    context.reset(NewContext());
    ASSERT_EQ(base::File::FILE_OK,
        AsyncFileTestHelper::Copy(file_system_context(),
            CreateURL(from_dir), CreateURL(to_dir)));

    EXPECT_TRUE(DirectoryExists(from_dir));
    EXPECT_TRUE(FileExists(from_file));
    EXPECT_EQ(1020, GetSize(from_file));
    EXPECT_TRUE(DirectoryExists(to_dir));
    EXPECT_TRUE(FileExists(to_file));
    EXPECT_EQ(1020, GetSize(to_file));
}

TEST_F(LocalFileUtilTest, MoveFile)
{
    const char* from_file = "fromfile";
    const char* to_file = "tofile";
    bool created;
    ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(from_file, &created));
    ASSERT_TRUE(created);
    std::unique_ptr<FileSystemOperationContext> context;

    context.reset(NewContext());
    ASSERT_EQ(base::File::FILE_OK,
        file_util()->Truncate(context.get(), CreateURL(from_file), 1020));

    EXPECT_TRUE(FileExists(from_file));
    EXPECT_EQ(1020, GetSize(from_file));

    context.reset(NewContext());
    ASSERT_EQ(base::File::FILE_OK,
        AsyncFileTestHelper::Move(file_system_context(),
            CreateURL(from_file),
            CreateURL(to_file)));

    EXPECT_FALSE(FileExists(from_file));
    EXPECT_TRUE(FileExists(to_file));
    EXPECT_EQ(1020, GetSize(to_file));
}

TEST_F(LocalFileUtilTest, MoveDirectory)
{
    const char* from_dir = "fromdir";
    const char* from_file = "fromdir/fromfile";
    const char* to_dir = "todir";
    const char* to_file = "todir/fromfile";
    bool created;
    std::unique_ptr<FileSystemOperationContext> context;

    context.reset(NewContext());
    ASSERT_EQ(base::File::FILE_OK,
        file_util()->CreateDirectory(context.get(), CreateURL(from_dir),
            false, false));
    ASSERT_EQ(base::File::FILE_OK, EnsureFileExists(from_file, &created));
    ASSERT_TRUE(created);

    context.reset(NewContext());
    ASSERT_EQ(base::File::FILE_OK,
        file_util()->Truncate(context.get(), CreateURL(from_file), 1020));

    EXPECT_TRUE(DirectoryExists(from_dir));
    EXPECT_TRUE(FileExists(from_file));
    EXPECT_EQ(1020, GetSize(from_file));
    EXPECT_FALSE(DirectoryExists(to_dir));

    context.reset(NewContext());
    ASSERT_EQ(base::File::FILE_OK,
        AsyncFileTestHelper::Move(file_system_context(),
            CreateURL(from_dir),
            CreateURL(to_dir)));

    EXPECT_FALSE(DirectoryExists(from_dir));
    EXPECT_TRUE(DirectoryExists(to_dir));
    EXPECT_TRUE(FileExists(to_file));
    EXPECT_EQ(1020, GetSize(to_file));
}

} // namespace content
